package org.wattdepot.cli.command;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import javax.xml.datatype.XMLGregorianCalendar;
import org.wattdepot.client.BadXmlException;
import org.wattdepot.client.MiscClientException;
import org.wattdepot.client.NotAuthorizedException;
import org.wattdepot.client.ResourceNotFoundException;
import org.wattdepot.client.WattDepotClient;
import org.wattdepot.util.tstamp.Tstamp;

/**
 * Implements the chart command.
 * 
 * @author George Lee
 *
 */
public class ChartCommand extends WattDepotCliCommand {
  /** Format string that will be written to the HTML file. */
  private static final String HTML_FORMAT_STRING = "<html><head><title>Chart for %s</title>"
                                              + "</head><body><h3>Power %s by %s from %s to %s"
                                              + "</h3><img src=\"%s\"/></body></html>";

  /**
   * Creates a new instance of the ChartCommand handler.
   * 
   * @param client The WattDepotClient that will process our data.
   */
  public ChartCommand(WattDepotClient client) {
    super(client);
  }

  /**
   * Creates a chart of the power consumed/generated for the specified source.
   * 
   * @param command The command to process.
   * @return The result of the command.
   * 
   * @see org.wattdepot.cli.command.WattDepotCliCommand#doCommand(String)
   */
  @Override
  public String doCommand(String command) {
    int interval;
    PowerCommandType type;
    
    StringTokenizer tokenizer = new StringTokenizer(command);
    List<String> tokens = new ArrayList<String>();
    while (tokenizer.hasMoreTokens()) {
      tokens.add(tokenizer.nextToken());
    }
    
    // Validate command.
    if (tokens.size() != 10) {
      return this.reportError(PARAMETER_COUNT_ERROR);
    }
    
    if (!"power".equals(tokens.get(1))) {
      return this.reportError(PARAMETER_ERROR + tokens.get(1));
    }
    
    if (!"sampling-interval".equals(tokens.get(6))) {
      return this.reportError(PARAMETER_ERROR + tokens.get(6));
    }
      
    if (!"file".equals(tokens.get(8))) {
      return this.reportError(PARAMETER_ERROR + tokens.get(8));
    }
    
    String option = tokens.get(2);
    
    if ("consumed".equals(option)) {
      type = PowerCommandType.POWER_CONSUMED;
    }
    else if ("generated".equals(option)) {
      type = PowerCommandType.POWER_GENERATED;
    }
    else {
      return this.reportError(PARAMETER_ERROR + tokens.get(2));
    }

    String source = tokens.get(3);

    XMLGregorianCalendar startTime, endTime;
    try {
      startTime = Tstamp.makeTimestamp(tokens.get(4));
      endTime = Tstamp.makeTimestamp(tokens.get(5));
      if (endTime.compare(startTime) <= 0) {
        return this.reportError("Start time must be less than end time.");
      }
    }
    catch (Exception e) {
      return this.reportError("Could not create timestamps.");
    }

    try {
      interval = Integer.valueOf(tokens.get(7));
      if (interval <= 0) {
        return this.reportError(INTERVAL_ERROR + tokens.get(7));
      }
    }
    catch (NumberFormatException e) {
      return this.reportError(INTERVAL_ERROR + tokens.get(7));
    }

    String filename = tokens.get(9);

    return createChart(type, source, startTime, endTime, interval, filename);
  }

  /**
   * Creates a chart of the power consumed/generated for the specified source.
   * 
   * @param type The type of chart to create (power generated or consumed).
   * @param sourceName The name of the source to get data from.
   * @param startTime The start time of the interval.
   * @param endTime The end time of the interval.
   * @param interval The sampling interval for the chart.
   * @param filename The filename to write to.
   * 
   * @return The result of the operation or an error message if it fails.
   */
  private String createChart(PowerCommandType type, String sourceName,
      XMLGregorianCalendar startTime, XMLGregorianCalendar endTime, int interval, String filename) {

    String result = "";

    try {
      // System.out.println("Retrieving data from WattDepot server.");
      String chartUri = this.createChartUri(type, sourceName, startTime, endTime, interval);
      // System.out.println("Got url " + chartUri + ", writing to file.");
      String filepath = this.writeChartToFile(chartUri, type, sourceName,
                                            startTime, endTime, filename);
      result = "Wrote: " + filepath;
    }
    catch (NotAuthorizedException e) {
      result = this.reportError(CONNECTION_ERROR);
    }
    catch (ResourceNotFoundException e) {
      result = this.reportError(SOURCE_ERROR + sourceName);
    }
    catch (BadXmlException e) {
      result = this.reportError(BAD_XML_ERROR);
    }
    catch (MiscClientException e) {
      result = this.reportError(CONNECTION_ERROR);
    }
    catch (IOException e) {
      result = "Could not write the file " + filename;
    }

    return result;
  }
  
  /**
   * Creates a chart of the power consumed/generated for the specified source. Uses example code
   * from:
   * 
   * http://code.google.com/p/wattdepot/wiki/WritingWattDepotClients
   * 
   * @param type Set to "POWER_CONSUMED" to generate a chart of the power consumed. Set to
   * "POWER_GENERATED" to generate a chart of the power generated.
   * @param sourceName The name of the source to retrieve data from.
   * @param startTime The start time of period to get the data from.
   * @param endTime The end time of the period to get the data from.
   * @param interval The sampling interval over which we retrieve data.
   * 
   * @return A Google Charts URI to the specified chart.
   * 
   * @throws NotAuthorizedException Thrown if we have no access to the WattDepot server.
   * @throws ResourceNotFoundException Thrown if the client cannot find the server.
   * @throws BadXmlException Thrown if the result we get back is bad.
   * @throws MiscClientException Thrown if there is an error retrieving the source.
   */
  private String createChartUri(PowerCommandType type, String sourceName,
      XMLGregorianCalendar startTime, XMLGregorianCalendar endTime, int interval)
      throws NotAuthorizedException, ResourceNotFoundException, BadXmlException,
      MiscClientException {
    double power = 0, minPower = Double.MAX_VALUE, maxPower = Double.MIN_VALUE;
    XMLGregorianCalendar timestamp;
    int maxUriLength = 2048;
    StringBuffer chartUri = new StringBuffer(maxUriLength);

    chartUri.append("http://chart.apis.google.com/chart?chs=250x100&cht=lc&chd=t:");
    timestamp = startTime;
    while (Tstamp.lessThan(timestamp, endTime)) {
      // Convert to megawatts
      if (type == PowerCommandType.POWER_GENERATED) {
        power = client.getPowerGenerated(sourceName, timestamp) / 1000000;
      }
      else {
        power = client.getPowerConsumed(sourceName, timestamp) / 1000000;
      }
      chartUri.append(String.format("%.1f,", power));
      if (power < minPower) {
        minPower = power;
      }
      if (power > maxPower) {
        maxPower = power;
      }
      timestamp = Tstamp.incrementMinutes(timestamp, interval);
    }
    // Delete trailing ','
    chartUri.deleteCharAt(chartUri.length() - 1);
    chartUri.append(String.format("&chds=%.1f,%.1f&chxt=y&chxr=0,%.1f,%.1f", minPower, maxPower,
        minPower, maxPower));
    return chartUri.toString();
  }

  /**
   * Gets the html file from the provided chartUri and creates a local file.
   * 
   * @param chartUri The URI to the chart.
   * @param type Whether the chart plots power consumed or generated.
   * @param source The power source used to create the chart.
   * @param start The start time of the chart.
   * @param end The end time of the chart.
   * @param filename The file to save.
   * 
   * @return The absolute path to the new file.
   * @throws IOException Thrown if there is an error while reading or writing the file.
   */
  private String writeChartToFile(String chartUri, PowerCommandType type, String source,
      XMLGregorianCalendar start, XMLGregorianCalendar end, String filename) throws IOException {
    File dataDir = new File("data");
    if (!dataDir.exists() && !dataDir.mkdir()) {
      throw new IOException("Could not make directory.");
    }
    
    String powerType;
    if (type == PowerCommandType.POWER_CONSUMED) {
      powerType = "consumed";
    }
    else {
      powerType = "generated";
    }
    File file = new File(dataDir + File.separator + filename);
    BufferedWriter out = new BufferedWriter(new FileWriter(file));

    out.write(String.format(HTML_FORMAT_STRING, source, powerType,
                                    source, start, end, chartUri));
    out.close();
    return file.getAbsolutePath();
  }

  /**
   * Gets the help string associated with the ChartCommand.
   * 
   * @see org.wattdepot.cli.command.WattDepotCliCommand#getHelp()
   * @return Information on how to invoke the chart command.
   */
  @Override
  public String getHelp() {
    return "chart power [generated|consumed] {source} {startday} {endday} "
      + "sampling-interval {minutes} file {file}\n"
      + "  Writes out an html file that will display a chart illustrating "
      + "the carbon or energy for the source over the interval.\n";
  }
}
